#!/usr/bin/env python
# -*- coding: utf-8 -*-

"""
文件监控模块

提供对文件和目录的监控功能，包括文件变化监控、目录变化监控等
"""

import os
import time
import threading
from pathlib import Path
from typing import Union, List, Dict, Any, Optional, Tuple, Callable, Set

# 导入异常处理装饰器
from ..exception.exception_handler import exception_handler
from ..exception.exceptions import FileException

# 导入日志模块
from ..logger.logger import Logger

# 创建日志记录器
logger = Logger("file_monitor").get_logger()


class FileChangeMonitor:
    """
    文件变化监控类
    
    监控文件或目录的变化，并在变化时触发回调函数
    """
    
    def __init__(self):
        """
        初始化文件变化监控器
        """
        self.running = False
        self.monitor_thread = None
        self.monitored_paths = {}
        self.lock = threading.Lock()
    
    @exception_handler(exception_type=Exception, reraise_as=FileException)
    def add_path(self, path: Union[str, Path], callback: Callable[[str, str], None], 
               recursive: bool = False, file_patterns: Optional[List[str]] = None,
               check_interval: float = 1.0) -> None:
        """
        添加要监控的路径
        
        Args:
            path: 要监控的文件或目录路径
            callback: 当文件变化时调用的回调函数，接收两个参数：事件类型和文件路径
                      事件类型可以是 'created', 'modified', 'deleted'
            recursive: 如果path是目录，是否递归监控子目录，默认为False
            file_patterns: 要监控的文件模式列表（支持通配符），默认为None表示监控所有文件
            check_interval: 检查间隔（秒），默认为1.0
            
        Raises:
            FileException: 添加监控路径失败时抛出
            
        Examples:
            >>> monitor = FileChangeMonitor()
            >>> def on_change(event_type, file_path):
            ...     print(f"{event_type}: {file_path}")
            >>> # 监控单个文件
            >>> monitor.add_path('config.ini', on_change)
            >>> # 监控目录中的所有.py文件
            >>> monitor.add_path('src', on_change, recursive=True, file_patterns=['*.py'])
            >>> # 开始监控
            >>> monitor.start()
            >>> # ... 程序运行 ...
            >>> # 停止监控
            >>> monitor.stop()
        """
        path = str(Path(path).absolute())
        
        # 检查路径是否存在
        if not os.path.exists(path):
            raise FileException(f"要监控的路径不存在: {path}")
        
        with self.lock:
            # 添加到监控路径
            self.monitored_paths[path] = {
                'callback': callback,
                'recursive': recursive,
                'file_patterns': file_patterns,
                'check_interval': check_interval,
                'last_check': time.time(),
                'state': self._get_path_state(path, recursive, file_patterns)
            }
        
        logger.info(f"添加监控路径: {path} (递归: {recursive})")
    
    @exception_handler(exception_type=Exception, reraise_as=FileException)
    def remove_path(self, path: Union[str, Path]) -> bool:
        """
        移除监控路径
        
        Args:
            path: 要移除的监控路径
            
        Returns:
            是否成功移除
            
        Raises:
            FileException: 移除监控路径失败时抛出
        """
        path = str(Path(path).absolute())
        
        with self.lock:
            if path in self.monitored_paths:
                del self.monitored_paths[path]
                logger.info(f"移除监控路径: {path}")
                return True
            else:
                logger.warning(f"要移除的监控路径不存在: {path}")
                return False
    
    @exception_handler(exception_type=Exception, reraise_as=FileException)
    def start(self) -> None:
        """
        开始监控
        
        Raises:
            FileException: 启动监控失败时抛出
        """
        if self.running:
            logger.warning("监控已经在运行中")
            return
        
        self.running = True
        self.monitor_thread = threading.Thread(target=self._monitor_thread, daemon=True)
        self.monitor_thread.start()
        
        logger.info("开始文件监控")
    
    @exception_handler(exception_type=Exception, reraise_as=FileException)
    def stop(self) -> None:
        """
        停止监控
        
        Raises:
            FileException: 停止监控失败时抛出
        """
        if not self.running:
            logger.warning("监控未在运行")
            return
        
        self.running = False
        if self.monitor_thread:
            self.monitor_thread.join(timeout=5.0)
            self.monitor_thread = None
        
        logger.info("停止文件监控")
    
    def _monitor_thread(self) -> None:
        """
        监控线程函数
        """
        while self.running:
            try:
                # 检查所有监控路径
                with self.lock:
                    paths_to_check = list(self.monitored_paths.items())
                
                for path, config in paths_to_check:
                    # 检查是否到达检查间隔
                    current_time = time.time()
                    if current_time - config['last_check'] < config['check_interval']:
                        continue
                    
                    try:
                        # 获取当前状态
                        current_state = self._get_path_state(
                            path, config['recursive'], config['file_patterns']
                        )
                        
                        # 比较状态变化
                        self._compare_states(
                            path, config['state'], current_state, config['callback']
                        )
                        
                        # 更新状态和最后检查时间
                        with self.lock:
                            if path in self.monitored_paths:
                                self.monitored_paths[path]['state'] = current_state
                                self.monitored_paths[path]['last_check'] = current_time
                    except Exception as e:
                        logger.error(f"监控路径时出错: {path} - {e}")
                
                # 短暂休眠以减少CPU使用
                time.sleep(0.1)
            except Exception as e:
                logger.error(f"监控线程出错: {e}")
    
    def _get_path_state(self, path: str, recursive: bool, file_patterns: Optional[List[str]]) -> Dict[str, float]:
        """
        获取路径的当前状态
        
        Args:
            path: 路径
            recursive: 是否递归
            file_patterns: 文件模式列表
            
        Returns:
            路径状态字典，键为文件路径，值为修改时间
        """
        state = {}
        
        # 如果是文件
        if os.path.isfile(path):
            try:
                state[path] = os.path.getmtime(path)
            except OSError:
                # 文件可能已被删除
                pass
        # 如果是目录
        elif os.path.isdir(path):
            try:
                # 遍历目录
                for root, dirs, files in os.walk(path):
                    # 如果不递归，跳过子目录
                    if not recursive and root != path:
                        continue
                    
                    # 处理文件
                    for file in files:
                        file_path = os.path.join(root, file)
                        
                        # 检查文件模式
                        if file_patterns:
                            import fnmatch
                            if not any(fnmatch.fnmatch(file, pattern) for pattern in file_patterns):
                                continue
                        
                        try:
                            state[file_path] = os.path.getmtime(file_path)
                        except OSError:
                            # 文件可能已被删除
                            pass
            except OSError:
                # 目录可能已被删除
                pass
        
        return state
    
    def _compare_states(self, base_path: str, old_state: Dict[str, float], 
                       new_state: Dict[str, float], callback: Callable[[str, str], None]) -> None:
        """
        比较状态变化并触发回调
        
        Args:
            base_path: 基础路径
            old_state: 旧状态
            new_state: 新状态
            callback: 回调函数
        """
        # 检查创建和修改
        for path, mtime in new_state.items():
            if path not in old_state:
                # 新创建的文件
                callback('created', path)
            elif mtime > old_state[path]:
                # 修改的文件
                callback('modified', path)
        
        # 检查删除
        for path in old_state:
            if path not in new_state:
                # 删除的文件
                callback('deleted', path)


class RealTimeFileMonitor:
    """
    实时文件监控类
    
    使用操作系统的文件系统事件通知机制实时监控文件变化
    
    注意：此类需要安装watchdog库
    pip install watchdog
    """
    
    def __init__(self):
        """
        初始化实时文件监控器
        """
        try:
            from watchdog.observers import Observer
            from watchdog.events import FileSystemEventHandler
            
            self.Observer = Observer
            self.FileSystemEventHandler = FileSystemEventHandler
        except ImportError as e:
            logger.error(f"初始化实时文件监控器失败: {e}")
            logger.error("请安装必要的依赖: pip install watchdog")
            raise ImportError("请安装必要的依赖: pip install watchdog") from e
        
        self.observer = None
        self.handlers = {}
        self.running = False
    
    @exception_handler(exception_type=Exception, reraise_as=FileException)
    def add_path(self, path: Union[str, Path], callback: Callable[[str, str], None], 
               recursive: bool = False, file_patterns: Optional[List[str]] = None) -> None:
        """
        添加要监控的路径
        
        Args:
            path: 要监控的文件或目录路径
            callback: 当文件变化时调用的回调函数，接收两个参数：事件类型和文件路径
                      事件类型可以是 'created', 'modified', 'deleted', 'moved'
            recursive: 如果path是目录，是否递归监控子目录，默认为False
            file_patterns: 要监控的文件模式列表（支持通配符），默认为None表示监控所有文件
            
        Raises:
            FileException: 添加监控路径失败时抛出
            
        Examples:
            >>> monitor = RealTimeFileMonitor()
            >>> def on_change(event_type, file_path):
            ...     print(f"{event_type}: {file_path}")
            >>> # 监控目录
            >>> monitor.add_path('src', on_change, recursive=True)
            >>> # 开始监控
            >>> monitor.start()
            >>> # ... 程序运行 ...
            >>> # 停止监控
            >>> monitor.stop()
        """
        path = str(Path(path).absolute())
        
        # 检查路径是否存在
        if not os.path.exists(path):
            raise FileException(f"要监控的路径不存在: {path}")
        
        # 创建事件处理器
        class CustomHandler(self.FileSystemEventHandler):
            def __init__(self, callback, file_patterns):
                self.callback = callback
                self.file_patterns = file_patterns
            
            def on_created(self, event):
                if not event.is_directory and self._match_pattern(event.src_path):
                    self.callback('created', event.src_path)
            
            def on_modified(self, event):
                if not event.is_directory and self._match_pattern(event.src_path):
                    self.callback('modified', event.src_path)
            
            def on_deleted(self, event):
                if not event.is_directory and self._match_pattern(event.src_path):
                    self.callback('deleted', event.src_path)
            
            def on_moved(self, event):
                if not event.is_directory:
                    if self._match_pattern(event.src_path):
                        self.callback('deleted', event.src_path)
                    if self._match_pattern(event.dest_path):
                        self.callback('created', event.dest_path)
            
            def _match_pattern(self, path):
                if not self.file_patterns:
                    return True
                
                import fnmatch
                filename = os.path.basename(path)
                return any(fnmatch.fnmatch(filename, pattern) for pattern in self.file_patterns)
        
        # 保存处理器
        handler = CustomHandler(callback, file_patterns)
        self.handlers[path] = {
            'handler': handler,
            'recursive': recursive
        }
        
        # 如果已经在运行，立即添加到观察者
        if self.running and self.observer:
            self.observer.schedule(handler, path, recursive=recursive)
        
        logger.info(f"添加实时监控路径: {path} (递归: {recursive})")
    
    @exception_handler(exception_type=Exception, reraise_as=FileException)
    def remove_path(self, path: Union[str, Path]) -> bool:
        """
        移除监控路径
        
        Args:
            path: 要移除的监控路径
            
        Returns:
            是否成功移除
            
        Raises:
            FileException: 移除监控路径失败时抛出
        """
        path = str(Path(path).absolute())
        
        if path in self.handlers:
            # 如果已经在运行，从观察者中移除
            if self.running and self.observer:
                for watch in list(self.observer._watches.keys()):
                    if watch.path == path:
                        self.observer.unschedule(watch)
            
            # 移除处理器
            del self.handlers[path]
            
            logger.info(f"移除实时监控路径: {path}")
            return True
        else:
            logger.warning(f"要移除的实时监控路径不存在: {path}")
            return False
    
    @exception_handler(exception_type=Exception, reraise_as=FileException)
    def start(self) -> None:
        """
        开始监控
        
        Raises:
            FileException: 启动监控失败时抛出
        """
        if self.running:
            logger.warning("实时监控已经在运行中")
            return
        
        # 创建观察者
        self.observer = self.Observer()
        
        # 添加所有处理器
        for path, config in self.handlers.items():
            self.observer.schedule(config['handler'], path, recursive=config['recursive'])
        
        # 启动观察者
        self.observer.start()
        self.running = True
        
        logger.info("开始实时文件监控")
    
    @exception_handler(exception_type=Exception, reraise_as=FileException)
    def stop(self) -> None:
        """
        停止监控
        
        Raises:
            FileException: 停止监控失败时抛出
        """
        if not self.running:
            logger.warning("实时监控未在运行")
            return
        
        # 停止观察者
        if self.observer:
            self.observer.stop()
            self.observer.join(timeout=5.0)
            self.observer = None
        
        self.running = False
        
        logger.info("停止实时文件监控")


class FileChangeTracker:
    """
    文件变化跟踪器
    
    跟踪文件的变化历史，记录文件的创建、修改和删除事件
    """
    
    def __init__(self, db_path: Optional[Union[str, Path]] = None):
        """
        初始化文件变化跟踪器
        
        Args:
            db_path: 数据库文件路径，用于持久化存储变化历史，默认为None表示不持久化
            
        Examples:
            >>> tracker = FileChangeTracker()
            >>> # 添加监控目录
            >>> tracker.add_path('src', recursive=True)
            >>> # 开始跟踪
            >>> tracker.start()
            >>> # ... 程序运行 ...
            >>> # 获取变化历史
            >>> history = tracker.get_history()
            >>> # 停止跟踪
            >>> tracker.stop()
        """
        self.db_path = db_path
        self.history = []
        self.monitor = RealTimeFileMonitor()
        self.running = False
        
        # 如果指定了数据库路径，尝试加载历史记录
        if db_path:
            self._load_history()
    
    @exception_handler(exception_type=Exception, reraise_as=FileException)
    def add_path(self, path: Union[str, Path], recursive: bool = False, 
               file_patterns: Optional[List[str]] = None) -> None:
        """
        添加要跟踪的路径
        
        Args:
            path: 要跟踪的文件或目录路径
            recursive: 如果path是目录，是否递归跟踪子目录，默认为False
            file_patterns: 要跟踪的文件模式列表（支持通配符），默认为None表示跟踪所有文件
            
        Raises:
            FileException: 添加跟踪路径失败时抛出
        """
        # 添加到监控器
        self.monitor.add_path(path, self._on_file_change, recursive, file_patterns)
        
        logger.info(f"添加跟踪路径: {path} (递归: {recursive})")
    
    @exception_handler(exception_type=Exception, reraise_as=FileException)
    def remove_path(self, path: Union[str, Path]) -> bool:
        """
        移除跟踪路径
        
        Args:
            path: 要移除的跟踪路径
            
        Returns:
            是否成功移除
            
        Raises:
            FileException: 移除跟踪路径失败时抛出
        """
        # 从监控器中移除
        result = self.monitor.remove_path(path)
        
        if result:
            logger.info(f"移除跟踪路径: {path}")
        
        return result
    
    @exception_handler(exception_type=Exception, reraise_as=FileException)
    def start(self) -> None:
        """
        开始跟踪
        
        Raises:
            FileException: 启动跟踪失败时抛出
        """
        if self.running:
            logger.warning("跟踪已经在运行中")
            return
        
        # 启动监控器
        self.monitor.start()
        self.running = True
        
        logger.info("开始文件变化跟踪")
    
    @exception_handler(exception_type=Exception, reraise_as=FileException)
    def stop(self) -> None:
        """
        停止跟踪
        
        Raises:
            FileException: 停止跟踪失败时抛出
        """
        if not self.running:
            logger.warning("跟踪未在运行")
            return
        
        # 停止监控器
        self.monitor.stop()
        self.running = False
        
        # 如果指定了数据库路径，保存历史记录
        if self.db_path:
            self._save_history()
        
        logger.info("停止文件变化跟踪")
    
    @exception_handler(exception_type=Exception, reraise_as=FileException)
    def get_history(self, path: Optional[Union[str, Path]] = None, 
                  event_type: Optional[str] = None,
                  start_time: Optional[float] = None,
                  end_time: Optional[float] = None) -> List[Dict[str, Any]]:
        """
        获取变化历史
        
        Args:
            path: 筛选特定路径的历史，默认为None表示所有路径
            event_type: 筛选特定事件类型的历史，可以是 'created', 'modified', 'deleted'，默认为None表示所有类型
            start_time: 筛选开始时间之后的历史，默认为None表示不限制开始时间
            end_time: 筛选结束时间之前的历史，默认为None表示不限制结束时间
            
        Returns:
            符合条件的变化历史列表
            
        Raises:
            FileException: 获取历史失败时抛出
        """
        # 筛选历史记录
        filtered_history = self.history
        
        if path:
            path = str(Path(path).absolute())
            filtered_history = [h for h in filtered_history if h['path'] == path]
        
        if event_type:
            filtered_history = [h for h in filtered_history if h['event_type'] == event_type]
        
        if start_time is not None:
            filtered_history = [h for h in filtered_history if h['timestamp'] >= start_time]
        
        if end_time is not None:
            filtered_history = [h for h in filtered_history if h['timestamp'] <= end_time]
        
        return filtered_history
    
    @exception_handler(exception_type=Exception, reraise_as=FileException)
    def clear_history(self) -> None:
        """
        清除变化历史
        
        Raises:
            FileException: 清除历史失败时抛出
        """
        self.history = []
        
        # 如果指定了数据库路径，保存空历史记录
        if self.db_path:
            self._save_history()
        
        logger.info("清除文件变化历史")
    
    def _on_file_change(self, event_type: str, file_path: str) -> None:
        """
        文件变化回调函数
        
        Args:
            event_type: 事件类型
            file_path: 文件路径
        """
        # 记录变化
        record = {
            'event_type': event_type,
            'path': file_path,
            'timestamp': time.time(),
            'datetime': time.strftime('%Y-%m-%d %H:%M:%S')
        }
        
        self.history.append(record)
        
        # 如果指定了数据库路径，定期保存历史记录
        if self.db_path and len(self.history) % 10 == 0:  # 每10条记录保存一次
            self._save_history()
    
    def _load_history(self) -> None:
        """
        从数据库加载历史记录
        """
        if not self.db_path:
            return
        
        db_path = Path(self.db_path)
        
        # 如果数据库文件不存在，创建空历史记录
        if not db_path.exists():
            self.history = []
            return
        
        try:
            import json
            with open(db_path, 'r', encoding='utf-8') as f:
                self.history = json.load(f)
            
            logger.info(f"从数据库加载历史记录成功: {len(self.history)} 条记录")
        except Exception as e:
            logger.error(f"从数据库加载历史记录失败: {e}")
            self.history = []
    
    def _save_history(self) -> None:
        """
        保存历史记录到数据库
        """
        if not self.db_path:
            return
        
        db_path = Path(self.db_path)
        
        # 确保父目录存在
        db_path.parent.mkdir(parents=True, exist_ok=True)
        
        try:
            import json
            with open(db_path, 'w', encoding='utf-8') as f:
                json.dump(self.history, f, ensure_ascii=False, indent=2)
            
            logger.info(f"保存历史记录到数据库成功: {len(self.history)} 条记录")
        except Exception as e:
            logger.error(f"保存历史记录到数据库失败: {e}")